#pragma once

#include <iostream>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

#include "../commen/log.hpp"
#include "../commen/util.hpp"

// 只负责代码的编译

namespace ns_compiler
{
    class compiler
    {
    public:
        compiler() {}

        ~compiler() {}

        // 返回值:编译成功返回true,编译失败返回false
        // 输入参数:只需要传入编译的文件名,我们的函数会自动的添加文件的后缀
        // 并且将编译好的程序还有错误信息放入temp文件夹中
        // 1234 -> ./temp/1234.cpp
        // 1234 -> ./temp/1234.exe
        // 1234 -> ./temp/1234.stderr
        static bool compile(const std::string &file_name)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                ns_log::LOG(ERROR) << "创建子进程失败!" << std::endl;
            }
            else if (pid == 0)
            {
                // 子进程
                // 系统默认的权限掩码是0022(可以将新创建的目录/文件将被屏蔽掉写和执行权限)
                // 我们为了直观的展示,所以这里设置权限掩码为0
                umask(0);

                int compiler_error = open(ns_util::path_util::compiler_error(file_name).c_str(),
                                          O_CREAT | O_WRONLY, 0664);
                if (compiler_error < 0)
                {
                    ns_log::LOG(WARNING) << "没有做成功形成compiler_error文件!" << std::endl;
                    exit(1);
                }

                // 重定向标准错误到compiler_error文件中
                dup2(compiler_error, 2);

                // 程序替换,并不影响进程的文件描述符表
                // 调用编译器,完成对代码的编译工作
                // -D的选项是预先定义宏,这样就可解决header和tail文件的拼接问题
                //也就是把测试用例当中的#include那边份给屏蔽掉
                execlp("g++", "g++", "-std=c++11", "-D", "COMPILER_ONLINE", "-o",
                       ns_util::path_util::exe(file_name).c_str(),
                       ns_util::path_util::src(file_name).c_str(), nullptr);

                // 如果到了这里说明程序替换失败了
                ns_log::LOG(ERROR) << "启动g++编译器失败!" << std::endl;
                exit(2);
            }
            else
            {
                // 父进程,不关心子进程的状态码,同时阻塞的等待
                // 这里的0不是意味着阻塞
                // 而是等待进程组ID与调用进程组ID相等的子进程
                waitpid(pid, nullptr, 0);

                // 查看编译是否成功,就看有没有对应的可执行程序
                // 这里实际在测试的时候,会有一点问题
                // 如果你已经有了这个可执行程序,但是你第二次编译的时候失败,它还是会提示你编译成功
                // 不过实际项目里我们一旦生成了可执行程序,运行完毕之后,会立马删除的
                // 所以这个问题不用担心
                if (ns_util::file_util::if_file_exists(ns_util::path_util::exe(file_name)))
                {
                    ns_log::LOG(INFO) << ns_util::path_util::src(file_name) << "编译成功!" << std::endl;
                    return true;
                }
            }

            ns_log::LOG(ERROR) << "编译失败,没有形成可执行程序!" << std::endl;
            return false;
        }
    };
}
